package com.asiainfo.indb.control;

import java.io.Closeable;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;

/**
 * 文件切割器，将指定大小文件根据字节数切割指定份额
 * 1、可指定是否保证行完整
 * 2、可跳过头行数
 *
 * @author zym
 * @date 2019-10-20 23:09
 */
public class FileCutter {

    private static final String DEF_ENCODE = "UTF-8";
    private static final String DEF_RAF_ENCODE = "ISO-8859-1";

    private LogService log;

    private int skipLineNum;
    private boolean keepLine;
    private int partNum;
    private Path file;

    /**
     * 当前计算结尾处
     */
    private volatile long curEnd;

    /**
     * 每份字节数
     */
    private long perSize;

    /**
     * 文件大小
     */
    private long fileSize;

    /**
     * 编码格式
     */
    private String encode;

    public FileCutter(Path file, int num) throws IOException {
        this(0, true, DEF_ENCODE, file, num, null);
    }

    /**
     * @param skipLineNum 跳过文件开始的行数，一般用于跳过头描述信息
     * @param keepLine    是否切分保持每行的完整，必须同时指定换行分隔符（未指定默认采用CRLF）
     * @param fileEncode  编码格式
     * @param file        文件
     * @param partNum     切分数量
     */
    public FileCutter(int skipLineNum, boolean keepLine, String fileEncode, Path file, int partNum, LogService log) throws IOException {
        this.skipLineNum = skipLineNum;
        this.keepLine = keepLine;
        this.encode = fileEncode;
        this.partNum = partNum;
        this.file = file;
        this.log = log;

        init();
    }

    /**
     * 初始化
     */
    private void init() throws IOException {
        this.fileSize = file.toFile().length();
        this.perSize = this.fileSize / this.partNum;

        // 计算
        if (skipLineNum > 0) {
            // 需要跳过头，否则就是开始
            try (RandomAccessFile raf = new RandomAccessFile(file.toFile(), "r")) {
                skipHeader(raf);
            }
        } else {
            curEnd = 0;
        }
    }

    /**
     * 跳过头几行
     *
     * @param raf 打开的文件
     */
    private void skipHeader(RandomAccessFile raf) throws IOException {
        log.report("入库文件需要跳过行数：" + skipLineNum);
        raf.seek(0);

        for (int i = 0; i < skipLineNum; i++) {
            raf.seek(toEndOfLine(raf.getFilePointer(), raf));
        }

        // 获取当前指针
        this.curEnd = raf.getFilePointer();
    }

    /**
     * 返下一个读取器
     *
     * @return 随机文件读取器
     */
    public synchronized FixedFileReader nextRandomAccessor() throws IOException {
        if (this.curEnd < this.fileSize) {
            RandomAccessFile raf = null;
            long startPointer = this.curEnd;
            this.curEnd = Math.min(this.curEnd + perSize, fileSize);

            try {
                raf = new RandomAccessFile(file.toFile(), "r");
                raf.seek(this.curEnd);

                if (keepLine) {
                    // 如果需要保持一行的完整
                    this.curEnd = toEndOfLine(this.curEnd, raf);
                }
            } catch (IOException e) {
                if (raf != null) {
                    raf.close();
                }
            }

            return new FixedFileReader(raf, startPointer, this.curEnd);
        }

        return null;
    }

    /**
     * 计算当前行结束点
     *
     * @param bp  开始指针
     * @param raf 文件
     * @return 结束点
     */
    private long toEndOfLine(long bp, RandomAccessFile raf) throws IOException {
        raf.seek(bp);
        raf.readLine();
        return raf.getFilePointer();
    }

    /**
     * 定长文件读取器
     */
    public class FixedFileReader implements Closeable {

        /**
         * 终止点
         */
        private long endPointer;

        private RandomAccessFile raf;

        private FixedFileReader(RandomAccessFile raf, long startPointer, long endPointer) throws IOException {
            this.raf = raf;
            this.raf.seek(startPointer);
            this.endPointer = endPointer;
        }

        /**
         * 读取，判断是否读取完成
         *
         * @return 读取结果
         * @throws IOException exp
         */
        public int read() throws IOException {
            if (raf.getFilePointer() >= endPointer) {
                return -1;
            }
            return raf.read();
        }

        /**
         * raf默认采用iso-8859-1编码，如果有中文则需要转码
         *
         * @return 行数据
         * @throws IOException exp
         */
        public String readLine() throws IOException {
            if (raf.getFilePointer() >= endPointer) {
                return null;
            }

            String line;
            if (keepLine) {
                line = raf.readLine();
            } else {
                StringBuffer input = new StringBuffer();
                int c = -1;
                boolean eol = false;

                while (!eol) {
                    switch (c = read()) {
                        case -1:
                        case '\n':
                            eol = true;
                            break;
                        case '\r':
                            eol = true;
                            long cur = raf.getFilePointer();
                            if ((read()) != '\n') {
                                raf.seek(cur);
                            }
                            break;
                        default:
                            input.append((char) c);
                            break;
                    }
                }

                if ((c == -1) && (input.length() == 0)) {
                    return null;
                }

                line = input.toString();
            }

            if (line != null) {
                return new String(line.getBytes(DEF_RAF_ENCODE), encode);
            }

            return null;
        }

        @Override
        public void close() throws IOException {
            raf.close();
        }
    }
}